/*
 * Copyright (c) 2024 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include "eeprom.h"
#include <chrono>
#include <mc/exception.h>
#include <thread>

using namespace dev;

bool chip_eeprom::init(mc::mutable_dict& csr_object) {
    try {
        // 调用基类的init函数
        if (!chip_base::init(csr_object)) {
            return false;
        }
        // 从字典中提取各个属性值并设置到对象中
        if (csr_object.contains("RwBlockSize")) {
            this->m_rw_block_size = csr_object["RwBlockSize"].as<uint16_t>();
        }
        if (csr_object.contains("WriteInterval")) {
            this->m_write_interval = csr_object["WriteInterval"].as<uint32_t>();
        }
        return true;
    } catch (const std::exception& e) {
        elog("EEPROM configuration loading failed: ${error}", ("error", e.what()));
        return false;
    }
}

void get_page_param(uint16_t& page_size, uint16_t& first_frag_len, uint32_t length,
                    uint32_t offset) {
    page_size      = page_size > EEPROM_MAX_PAGE_SIZE ? EEPROM_DEFAULT_PAGE_SIZE : page_size;
    first_frag_len = page_size - offset % page_size;
    first_frag_len = first_frag_len > length ? length : first_frag_len;
}

void get_write_param(uint16_t& page_size, uint16_t& first_frag_len, uint32_t& write_interval,
                     uint32_t length, uint32_t offset) {
    get_page_param(page_size, first_frag_len, length, offset);
    write_interval =
        write_interval > EEPROM_MAX_WRITE_INTERVAL ? EEPROM_DEFAULT_WRITE_INTERVAL : write_interval;
}

int32_t chip_eeprom::block_read(uint32_t offset, uint32_t length, std::vector<uint8_t>& in_buffer,
                                 std::vector<uint8_t>& out_buffer, std::string& error) {
    try {
        // 检查前级总线是否存在
        if (!m_host_bus || !m_left_bus) {
            error = "pre bus object not found";
            MC_THROW(mc::invalid_op_exception, error);
        }

        out_buffer.clear();
        int32_t  ret = HAS_OK;
        uint16_t first_frag_len;
        get_page_param(m_rw_block_size, first_frag_len, length, offset);
        if (first_frag_len == length) {
            ret = data_access(0, offset, length, in_buffer, out_buffer, CHIP_OP_TYPE_READ);
            if (ret != HAS_OK) {
                error = "EEPROM read failed: offset=" + std::to_string(offset) + ", length=" + std::to_string(length) + ", error=" + std::to_string(ret);
                elog("${error}", ("error", error));
                return ret;
            }
            return HAS_OK;
        }

        uint32_t             next_read_offset = offset;
        uint32_t             next_read_length = first_frag_len;
        uint32_t             remain_length    = length;
        std::vector<uint8_t> read_buffer(next_read_length);

        while (remain_length > 0) {
            uint32_t read_offset = next_read_offset;
            uint32_t read_length = next_read_length;
            remain_length -= read_length;
            next_read_offset += read_length;
            next_read_length = remain_length > m_rw_block_size ? m_rw_block_size : remain_length;

            ret =
                data_access(0, read_offset, read_length, in_buffer, read_buffer, CHIP_OP_TYPE_READ);
            if (ret != HAS_OK) {
                error = "EEPROM read failed: offset=" + std::to_string(read_offset) + ", length=" + std::to_string(read_length) + ", error=" + std::to_string(ret);
                elog("${error}", ("error", error));
                return ret;
            }
            out_buffer.insert(out_buffer.end(), read_buffer.begin(),
                              read_buffer.begin() + read_length);
        }

        return HAS_OK;
    } catch (const std::exception& e) {
        error = std::string("EEPROM read failed: offset=") + std::to_string(offset) + ", length=" + std::to_string(length) + ", error=" + e.what();
        elog("${error}", ("error", error));
        return HAS_ERR;
    }
}

int32_t chip_eeprom::block_write(uint32_t offset, const std::vector<uint8_t>& in_buffer,
                                  std::vector<uint8_t>& out_buffer, std::string& error) {
    try {
        // 检查前级总线是否存在
        if (!m_host_bus || !m_left_bus) {
            error = "pre bus object not found";
            MC_THROW(mc::invalid_op_exception, error);
        }

        int32_t  ret = HAS_OK;
        uint16_t first_frag_len;
        get_write_param(m_rw_block_size, first_frag_len, m_write_interval, in_buffer.size(),
                        offset);
        if (first_frag_len == in_buffer.size()) {
            ret =
                data_access(0, offset, in_buffer.size(), in_buffer, out_buffer, CHIP_OP_TYPE_WRITE);
            if (ret != HAS_OK) {
                error = "EEPROM write failed: offset=" + std::to_string(offset) + ", length=" + std::to_string(in_buffer.size()) + ", error=" + std::to_string(ret);
                elog("${error}", ("error", error));
                return ret;
            }
            return HAS_OK;
        }

        uint32_t             next_write_offset = offset;
        uint32_t             next_write_length = first_frag_len;
        uint32_t             remain_length     = in_buffer.size();
        uint32_t             current_length    = 0;
        std::vector<uint8_t> write_buffer(next_write_length);

        while (remain_length > 0) {
            uint32_t write_offset = next_write_offset;
            uint32_t write_length = next_write_length;
            write_buffer.assign(in_buffer.begin() + current_length,
                                in_buffer.begin() + current_length + write_length);
            remain_length -= write_length;
            next_write_offset += write_length;
            next_write_length = remain_length > m_rw_block_size ? m_rw_block_size : remain_length;
            current_length += write_length;

            ret = data_access(0, write_offset, write_length, write_buffer, out_buffer,
                              CHIP_OP_TYPE_WRITE);
            if (ret != HAS_OK) {
                error = "EEPROM write failed: offset=" + std::to_string(write_offset) + ", length=" + std::to_string(write_length) + ", error=" + std::to_string(ret);
                elog("${error}", ("error", error));
                return ret;
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(m_write_interval));
        }

        return HAS_OK;
    } catch (const std::exception& e) {
        error = std::string("EEPROM write failed: offset=") + std::to_string(offset) + ", length=" + std::to_string(in_buffer.size()) + ", error=" + e.what();
        elog("${error}", ("error", error));
        return HAS_ERR;
    }
}
